﻿using System;
using System.Collections;
using System.Collections.Generic;
using Drill.Core;
using Drill.Extensibility;
using StructureMap;

namespace Drill.DrillBits.StructureMap
{
	public class StructureMapDrillBit : IDrillBit
    {
		private readonly IDrillBitInitContext _context;
		private IContainer _container;

        /// <summary>
		/// Initializes a new instance of the <see cref="StructureMapDrillBit"/> class.
        /// </summary>
		public StructureMapDrillBit(IDrillBitInitContext context)
			: this(ObjectFactory.Container, context)
        {
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="StructureMapDrillBit" /> class.
		/// </summary>
		/// <param name="container">The wrapped unity container.</param>
		/// <param name="context">The context.</param>
		/// <exception cref="System.ArgumentNullException">container</exception>
		protected StructureMapDrillBit(IContainer container, IDrillBitInitContext context)
		{
			if (null == container)
			{
				throw new ArgumentNullException("container");
			}
			_container = container;
			_context = context;
			Configure();
		}

		/// <summary>
		/// Gets the <see cref="IContainer" /> instance that provides the container implementation.
		/// </summary>
		/// <value>An instance of the <see cref="IContainer" /> interface that provides the container implementation.</value>
		protected IContainer InnerContainer
		{
			get
			{
				CheckDisposed();
				return _container;
			}
		}

		#region " IDependencyResolver Interface Implementation "

		/// <summary>
		/// Gets an instance of the type specified by <paramref name="targetType" />.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <returns>
		/// An instance of the requested type.
		/// </returns>
		/// <exception cref="System.ArgumentNullException">targetType</exception>
		public object GetInstance(Type targetType)
		{
			CheckDisposed();
			if (null == targetType)
			{
				throw new ArgumentNullException("targetType");
			}
			return _container.GetInstance(targetType);
		}

		/// <summary>
		/// Gets a named instance of the type specified by <paramref name="targetType" />.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <param name="name">The name of the specific object instance.</param>
		/// <returns>
		/// An instance of the requested named type.
		/// </returns>
		/// <exception cref="System.ArgumentNullException">targetType</exception>
		public object GetInstance(Type targetType, string name)
		{
			CheckDisposed();
			if (null == targetType)
			{
				throw new ArgumentNullException("targetType");
			}
			return _container.GetInstance(targetType, name);
		}

		/// <summary>
		/// Get all instances of the type specified by <paramref name="targetType" />.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <returns>
		/// An <see cref="IEnumerable" /> instance representing all
		/// available instances of the requested type.
		/// </returns>
		/// <exception cref="System.ArgumentNullException">targetType</exception>
		public IEnumerable GetAllInstances(Type targetType)
		{
			CheckDisposed();
			if (null == targetType)
			{
				throw new ArgumentNullException("targetType");
			}
			return _container.GetAllInstances(targetType);
		}

		/// <summary>
		/// Gets an instance of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <returns>An instance of the requested type.</returns>
		public TTargetType GetInstance<TTargetType>()
		{
			CheckDisposed();
			return _container.GetInstance<TTargetType>();
		}

		/// <summary>
		/// Gets a named instance of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <param name="name">Name the object was registered with.</param>
		/// <returns>An instance of the requested named type.</returns>
		public TTargetType GetInstance<TTargetType>(string name)
		{
			CheckDisposed();
			return _container.GetInstance<TTargetType>(name);
		}

		/// <summary>
		/// Get all instances of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <returns>
		/// An <see cref="IEnumerable{TTargetType}"/> instance representing all 
		/// available instances of the requested type.
		/// </returns>
		public IEnumerable<TTargetType> GetAllInstances<TTargetType>()
		{
			CheckDisposed();
			return _container.GetAllInstances<TTargetType>();
		}

		#endregion " IDependencyResolver Interface Implementation "

		#region " IDrillBit Interface Implementation "

		/// <summary>
		/// Gets the object the DrillBit uses to resolve types.
		/// </summary>
		/// <value>
		/// The object the DrillBit uses to resolve types.
		/// </value>
		/// <remarks>
		/// The resolver object is usually the container object when the DrillBit wraps a DI container.
		/// It may also some other type of object such as a service locator object or any other type of object imaginable.
		/// </remarks>
		public object ResolverObject
		{
			get
			{
				CheckDisposed();
				return _container;
			}
		}

		#endregion " IDrillBit Interface Implementation "

		#region " IDisposable Interface and Disposable Pattern Implementation "

		/// <summary>
		/// Gets a value indicating whether this instance is disposed.
		/// </summary>
		/// <value><c>true</c> if this instance is disposed; otherwise, <c>false</c>.</value>
		public bool IsDisposed { get; private set; }

		/// <summary>
		/// Performs managed and unmanaged resource cleanup.
		/// </summary>
		public void Dispose()
		{
			// NOTE: The Dispose() method is public.
			// NOTE: The Dispose() method is not virtual because it should not be overridden.

			if (false == IsDisposed)
			{
				Dispose(true);
				GC.SuppressFinalize(this);
			}
		}

		/// <summary>
		/// Performs managed and unmanaged resource cleanup.
		/// </summary>
		/// <param name="disposing"><c>true</c> if disposing, <c>false</c> if finalizing.</param>
		private void Dispose(bool disposing)
		{
			// NOTE: The Dispose(bool disposing) method is private.
			// NOTE: The Dispose(bool disposing) method is not virtual (because it is private).

			// When the operation is Dispose, call DisposeManagedResources AND DisposeUnmanagedResources.
			// When the operation is Finalize, only call DisposeUnmanagedResources.

			if (false == IsDisposed)
			{
				if (disposing)
				{
					DisposeManagedResources();
				}

				DisposeUnmanagedResources();

				// Set the _isDisposed flag.
				IsDisposed = true;
			}
		}

		protected virtual void DisposeManagedResources()
		{
			// NOTE: The DisposeManagedResources() method is protected.
			// NOTE: The DisposeManagedResources() method is virtual.

			// Clean up managed resources here.
			// * Call Dispose on all disposable object references
			// * Unsubscribe from all subscribed events
			// * Set managed object references to null

			// Dispose the container.
			if (null != _container)
			{
				_container.Dispose();
			}

			// Release the reference to the container.
			_container = null;
		}

		protected virtual void DisposeUnmanagedResources()
		{
			// NOTE: The DisposeUnmanagedResources() method is protected.
			// NOTE: The DisposeUnmanagedResources() method is virtual.

			// Clean up unmanaged resources here.
			// * Release handles
			// * Free allocated memory
			// * Dereference COM objects
			// * Set large fields to null
		}

		private void CheckDisposed()
		{
			if (IsDisposed)
			{
				string objectName = string.Format("{0}[\"{1}\"]", GetType().Name, _context.Name);

				throw new ObjectDisposedException(objectName, string.Format("{0} has been disposed.", objectName));
			}
		}

		#endregion " IDisposable Interface and Disposable Pattern Implementation "

		private void Configure()
		{
			if (null == _context || null == _context.ConfigurationSources || _context.ConfigurationSources.Count < 1)
			{
				return;
			}

			foreach (IConfigurationSource configurationSource in _context.ConfigurationSources)
			{
				if (null == configurationSource)
				{
					continue;
				}

				bool isConfigured;

				try
				{
					switch (configurationSource.ConfigurationMethod)
					{
						case DrillBitConfigMethod.Default:
							ConfigureUsingDefaultConfigurationMethod();
							isConfigured = true;
							break;

						case DrillBitConfigMethod.DrillBitSetup:
							ConfigureUsingSetupProviderConfigurationMethod(configurationSource);
							isConfigured = true;
							break;

						default:
							isConfigured = false;
							break;
					}
				}
				catch (Exception ex)
				{
					if (ex is DependencyResolverException)
					{
						throw;
					}

					throw
						new StructureMapDrillBitException(
							ex
						  , "A unexpected exception occurred while configuring the '{0}' DrillBit "
						  + "using the '{1}' configuration source. "
						  + "See the inner exception for details."
						  , _context.Name
						  , configurationSource.Key
							);
				}

				if (false == isConfigured)
				{
					throw new StructureMapDrillBitException(
						"'{0}' does not support the '{1}' configuration method."
					  , GetType().FullName
					  , configurationSource.ConfigurationMethod
					  );
				}
			}
		}

		private void ConfigureUsingDefaultConfigurationMethod()
		{
			ObjectFactory.Configure(x => x.Scan(a =>
				{
					a.AssembliesFromApplicationBaseDirectory();
					a.WithDefaultConventions();
				}));
		}

		private void ConfigureUsingSetupProviderConfigurationMethod(IConfigurationSource configurationSource)
		{
			IDrillBitSetup configurationProvider =
				configurationSource.ConfigurationObject as IDrillBitSetup;

			if (null == configurationProvider)
			{
				throw new StructureMapDrillBitException(
					"The IDrillBitSetup instance was null for the '{0}' "
				  + "configuration method of the '{1}' dependency resolver. "
				  + "The dependency resolver cannot be configured."
				  , configurationSource.ConfigurationMethod
				  , _context.Name
					);
			}

			configurationProvider.Configure(this, _container);
		}
	}
}
